package config

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/crypto"
	"hundsun.com/hsl/hschain/protos"
	"hundsun.com/hsl/hschain/protos/consensus/raft"
	"hundsun.com/hsl/hschain/protos/parachain"
)

// ##############GenesisTransactionPayload##############

const (
	// MaxTxSizeBytesLimit 创世块参数：交易最小字节限制，即不能小于该限制
	MaxTxSizeBytesLimit = 1024
	// MaxBlockSizeBytesLimit 创世块参数：区块最小字节限制，即不能小于该限制
	MaxBlockSizeBytesLimit = 4096
	// MaxTxCountLimit 创世块参数：交易数限制，即不能小于该限制
	MaxTxCountLimit = 1
	// MaxTimeIntervalErrorLimit 创世块参数：时间误差限制，即不能小于该限制
	MaxTimeIntervalErrorLimit = 3
)

// SoloConsensusType 共识类型
const SoloConsensusType = "solo"

// DposConsensusType 共识类型
const DposConsensusType = "dpos"

var (
	hashHexRegexp *regexp.Regexp
)

// Validate ...
func (m *GenesisTransactionPayload) Validate() error {
	if m.Crypto == nil {
		return errors.New("crypto  config cannot be empty")
	}
	if m.Consensus == nil {
		return errors.New("consensus config cannot be empty")
	}
	if m.CheckBlock == nil {
		return errors.New("check block config cannot be empty")
	}

	if m.CheckBlock.Enabled && m.CheckBlock.Distance == 0 {
		return errors.New("CheckBlockDistance must be positive")
	}

	if m.BlockLimit == nil {
		return errors.New("blockLimit cannot be empty")
	}

	if m.TimeLimit == nil {
		return errors.New("timeLimit cannot be empty")
	}

	err := m.BlockLimit.Validate()
	if err != nil {
		return err
	}

	err = m.TimeLimit.Validate()
	if err != nil {
		return err
	}

	err = m.Crypto.Validate()
	if err != nil {
		return err
	}
	hashSize, err := crypto.HashSize()
	if err != nil {
		return err
	}
	hashHexRegexp = regexp.MustCompile("^[a-zA-Z0-9]{" + strconv.Itoa(hashSize*2) + "}$")

	err = m.Consensus.Validate()
	if err != nil {
		return err
	}

	//平行链条的共识方式只能在平行链中使用
	if m.ChainID == protos.MainChainID {
		if m.Consensus.Type == parachain.ConsensusType {
			return fmt.Errorf("'%s' consensus can not be used in main chain", parachain.ConsensusType)
		}
	} else {
		if m.Consensus.Type != parachain.ConsensusType {
			return fmt.Errorf("only '%s' consensus can be used in para chain", parachain.ConsensusType)
		}
	}

	//err = m.SequenceCenter.PeerID.Validate()
	//if err != nil {
	//	return err
	//}

	if m.SequenceCenter != nil {
		err = (protos.Address(m.SequenceCenter.SignerAddress)).Validate()
		if err != nil {
			return err
		}
	}

	for _, ab := range m.Alloc {
		err = ab.Validate()
		if err != nil {
			return err
		}
	}

	if m.ParaChainConfig != nil {
		return m.ParaChainConfig.Validate()
	}

	return nil
}

// ##############CryptoConfig##############

// Validate ...
func (m *CryptoConfig) Validate() error {
	if m.AsymmetricAlgorithm == "" {
		return errors.New("crypto.asymmetricAlgorithm cannot be empty")
	}
	if m.HashFamily == "" {
		return errors.New("crypto.hashFamily cannot be empty")
	}
	if m.SymmetricAlgorithm == "" {
		return errors.New("crypto.symmetricAlgorithm cannot be empty")
	}
	crypto.Init(func(c *crypto.Options) {
		c.AsymmetricAlgorithm = m.AsymmetricAlgorithm
		c.HashFamily = m.HashFamily
		c.SymmetricAlgorithm = m.SymmetricAlgorithm
	})
	testData := []byte("hundsun")
	_, err := crypto.KeyPairGen()
	if err != nil {
		return err
	}
	_, err = crypto.KeyGen()
	if err != nil {
		return err
	}
	_, err = crypto.Hash(testData)
	if err != nil {
		return err
	}
	return nil
}

// ##############ConsensusConfig##############

// Validate ...
func (m *ConsensusConfig) Validate() error {
	switch m.Type {
	case DposConsensusType:
		var payload DposConsensusConfigPayload
		err := proto.Unmarshal(m.Payload, &payload)
		if err != nil {
			return err
		}
		return payload.Validate()
	case SoloConsensusType:
		var payload SoloConsensusConfigPayload
		err := proto.Unmarshal(m.Payload, &payload)
		if err != nil {
			return err
		}
		return payload.Validate()
	case raft.ConsensusType:
		var payload raft.ConsensusConfigPayload
		err := proto.Unmarshal(m.Payload, &payload)
		if err != nil {
			return err
		}
		return payload.Validate()
	case parachain.ConsensusType:
		var payload parachain.ConsensusConfigPayload
		err := proto.Unmarshal(m.Payload, &payload)
		if err != nil {
			return err
		}
		return payload.Validate()
	default:
		return fmt.Errorf("unsupported consensus type '%s'", m.Type)
	}
}

// ##############BlockLimit##############

// Validate ...
func (m *BlockLimit) Validate() error {
	if m.MaxTxSizeBytes < MaxTxSizeBytesLimit {
		return errors.New("blockLimit.maxTxSizeBytes cannot be less than 1KB")
	}
	if m.MaxBlockSizeBytes < MaxBlockSizeBytesLimit {
		return errors.New("blockLimit.maxBlockSizeBytes cannot be less than 4KB")
	}
	if m.MaxTxCount < MaxTxCountLimit {
		return errors.New("blockLimit.maxTxCount must be positive")
	}
	return nil
}

// ##############TimeLimit##############

// Validate ...
func (m *TimeLimit) Validate() error {
	if m.MaxTimeIntervalError < MaxTimeIntervalErrorLimit {
		return errors.New("timeLimit.maxTimeIntervalError cannot be less than 3")
	}
	return nil
}

// ##############ParaChainConfig##############

// Validate ...
func (m *ParaChainConfig) Validate() error {
	if m.MaxChainCount == 0 {
		return nil
	}

	if m.MaxChainCount > parachain.ParaChainCountHardLimit {
		return fmt.Errorf("paraChain.maxChainCount exceed %d", parachain.ParaChainCountHardLimit)
	}

	if m.MaxValidatorCountPerChain == 0 {
		return errors.New("paraChain.maxValidatorCountPerChain cannot be zero")
	}

	if m.MaxValidatorCountPerChain > parachain.ValidatorsPerChainCountHardLimit {
		return fmt.Errorf("paraChain.maxValidatorCountPerChain exceed %d", parachain.ValidatorsPerChainCountHardLimit)
	}

	if m.ValidatorDeposit == 0 {
		return fmt.Errorf("paraChain.ValidatorDeposit cannot be zero")
	}
	return nil
}

// Validate ...
func (p *SoloConsensusConfigPayload) Validate() error {
	if len(p.Address) == 0 {
		return errors.New("minerAddress of ConsensusConfigPayload[type=solo] cannot be empty")
	}
	minerAddress := protos.Address(p.Address)
	return minerAddress.Validate()
}

// Validate ...
func (p *DposConsensusConfigPayload) Validate() error {
	if p.MinerCount%2 == 0 {
		return errors.New("'minerCount'  of consensus config  must be an odd number")
	}

	if p.MinerCount == 1 {
		return errors.New("'minerCount'  of consensus config  must be greater than or equal to 3")
	}

	if len(p.DefaultMiners) != int(p.MinerCount) {
		return fmt.Errorf("size of DefaultMiners must be %d", p.MinerCount)
	}

	for _, m := range p.DefaultMiners {
		err := m.Validate()
		if err != nil {
			return err
		}
	}

	minerAddrMap := make(map[string]bool)
	for _, m := range p.DefaultMiners {
		if minerAddrMap[m.Address] {
			return fmt.Errorf("duplicated miner '%s'", m.Address)
		}
		minerAddrMap[m.Address] = true
	}

	minerNetAddrMap := make(map[string]bool)
	for _, m := range p.DefaultMiners {
		peerID := m.PeerID
		if minerNetAddrMap[peerID] {
			return fmt.Errorf("duplicated miner network peer id '%s'", peerID)
		}
		minerNetAddrMap[peerID] = true
	}

	if p.TimeSliceLength == 0 {
		return errors.New("'TimeSliceLength' of consensus config must be positive ")
	}

	if p.MaxBlocksInTimeSlice == 0 {
		return errors.New("'MaxBlocksInTimeSlice' of consensus config must be positive ")
	}

	return nil
}
